package com.ctcemti.mdm.framework.config;

import com.alibaba.fastjson.JSON;
import com.ctcemti.mdm.common.result.Result;
import com.ctcemti.mdm.common.util.DateUtil;
import com.ctcemti.mdm.common.util.JWTUtil;
import com.ctcemti.mdm.project.sys.dao.ErrorLogMapper;
import com.ctcemti.mdm.project.sys.dao.OperationLogMapper;
import com.ctcemti.mdm.project.sys.entity.ErrorLog;
import com.ctcemti.mdm.project.sys.entity.OperationLog;
import com.google.common.collect.Maps;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName WebControllerAop
 * @Description 操作日志aop切面
 * @Author bxf
 * @Date 16:38 2020/2/7
 * @Version V1.0
 **/
@Component
@Aspect
public class WebControllerAop {

	@Autowired
	private OperationLogMapper operationLogMapper;

	@Autowired
	private ErrorLogMapper errorLogMapper;

	@Pointcut("@annotation(com.ctcemti.mdm.project.common.annotation.SysLog)")
	public void optateService() {

	}

	@Pointcut("execution(* com.ctcemti.mdm.project.sys.controller..*.*(..))")
	public void executeService() {

	}

	/**
	 * 前置通知，方法调用前被调用
	 * 
	 * @param joinPoint
	 */
	@Before("executeService()")
	public void doBeforeAdvice(JoinPoint joinPoint) {
		//System.out.println("我是前置通知!!!");
	}

	/**
	 * 后置返回通知 这里需要注意的是: 如果参数中的第一个参数为JoinPoint，则第二个参数为返回值的信息
	 * 如果参数中的第一个参数不为JoinPoint，则第一个参数为returning中对应的参数 returning
	 * 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知，否则不执行，
	 * 对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
	 * 
	 * @param joinPoint
	 * @param keys
	 * @throws NoSuchMethodException 
	 * @throws IntrospectionException 
	 * @throws InvocationTargetException 
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 */
	@AfterReturning(value = "executeService()", returning = "keys") // 过滤所有的接口，记录操作错误信息
	public void doAfterReturningAdvice(JoinPoint joinPoint, Object keys) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, IllegalArgumentException, IntrospectionException{
		changeDataLog(joinPoint, keys);
	}

	public void changeDataLog(JoinPoint joinPoint, Object keys) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, IllegalArgumentException, IntrospectionException{

		// 获取RequestAttributes
		RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
		// 从获取RequestAttributes中获取HttpServletRequest的信息
		HttpServletRequest request = (HttpServletRequest) requestAttributes
				.resolveReference(RequestAttributes.REFERENCE_REQUEST);
		HttpSession session = request.getSession();
		//Employee employee = (Employee) session.getAttribute(SysConstant.SESSION_NAME);// 获取session
		String token = request.getHeader("Authorization");

		if (!StringUtils.isEmpty(token)) {

			String loginId = JWTUtil.getLoginId(token);
			String loginName = JWTUtil.getLoginName(token);
			// 获取目标方法的参数信息
			Object[] obj = joinPoint.getArgs();
			// AOP代理类的信息
			joinPoint.getThis();
			// 代理的目标对象
			joinPoint.getTarget();
			// 用的最多 通知的签名
			MethodSignature signature = (MethodSignature) joinPoint.getSignature();
			// 代理的是哪一个方法
			String methodName = signature.getName();
			// AOP代理类的名字
			String actioncontroller = signature.getDeclaringTypeName();
			// AOP代理类的类（class）信息
			signature.getDeclaringType();
			//Map<String, Object> map = (Map<String, Object>) session.getAttribute(SysConstant.PROJECT_NAME);// 获取session

			// 如果要获取Session信息的话，可以这样写：
			// HttpSession session = (HttpSession)
			// requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
			Enumeration<String> enumeration = request.getParameterNames();
			Map<String, String> parameterMap = Maps.newHashMap();
			// 得到了方法,提取出他的注解
			Method method = signature.getMethod();

			if (!(keys instanceof String)) {
				@SuppressWarnings("rawtypes")
				Map<String,Object> json = convertBeanToMap(keys);// 获取返回内容
				ErrorLog errorLog = new ErrorLog();
				String company = "";
				if (keys != null && !"2000".equals(String.valueOf(json.get("code")))) {
					// 操作错误日志
					errorLog.setErrorCode(StringUtils.isEmpty(json.get("code")) ? "1010" : String.valueOf(json.get("code")));
					errorLog.setOperateTime(new Date());
					errorLog.setErrorContent(
							company + (StringUtils.isEmpty(json.get("msg")) ? "未知错误，请联系管理员！" : String.valueOf(json.get("msg"))));
					errorLog.setOperator(loginId);
					errorLogMapper.insertErrorLog(errorLog);
				}

				// 操作类型
				String myaction = "";

				// 判断该方法上的注入
				ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);//获取swagger方法注释
				if(apiOperation!=null){
					// 方法名称
					String functionName = apiOperation.value();

					String name = method.getName();

					if(!StringUtils.isEmpty(name) && ((name.toLowerCase().contains("insert")||name.toLowerCase().contains("insert")))||name.toLowerCase().contains("update")||(name.toLowerCase().contains("delete") || name.toLowerCase().contains("remove"))){
						if(name.toLowerCase().contains("insert")||name.toLowerCase().contains("add")){
							myaction = "添加操作";
						}else if(name.toLowerCase().contains("update")){
							myaction = "更新操作";
						}else if(name.toLowerCase().contains("delete") || name.toLowerCase().contains("remove")){
							myaction = "删除操作";
						}
						// System.out.println("注解式拦截" + action.name());
						while (enumeration.hasMoreElements()) {
							String parameter = enumeration.nextElement();
							parameterMap.put(parameter, request.getParameter(parameter));
						}
						String str = JSON.toJSONString(parameterMap);
						OperationLog operationLog = new OperationLog();
						operationLog.setFunctionName(functionName);
						operationLog.setOperator(loginId);
						operationLog.setOperateTime(new Date());
						//operationLog.setModuleId(String.valueOf(session.getAttribute(SysConstant.MENU_NAME)));
						if (keys != null && "2000".equals(String.valueOf(json.get("code")))) {
							operationLog.setOperationContent(company + "--<<>>--" + myaction + ":" + loginName
									+ "(id为" + loginId + ")在"
									+ DateUtil.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss") + "调用了" + actioncontroller
									+ "中的" + methodName + "方法;请求参数为：" + str + ";操作结果：成功");
							operationLogMapper.insertOperationLog(operationLog);// 操作日志
						}

					}
				}


			}
		}

	}

	/**
	 * 后置异常通知 定义一个名字，该名字用于匹配通知实现方法的一个参数名，当目标方法抛出异常返回后，将把目标方法抛出的异常传给通知方法；
	 * throwing 限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知，否则不执行，
	 * 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
	 * 
	 * @param joinPoint
	 * @param exception
	 */
	@AfterThrowing(value = "executeService()", throwing = "exception")
	public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
		exception.printStackTrace();
	}
	
	/**
	 * 将实体类转为map集合
	 * <p>Title: convertBeanToMap</p>  
	 * <p>Description: </p>  
	 * @author bxf  
	 * @date 2019年3月22日 上午11:54:54    
	 * @param bean
	 * @return
	 * @throws NoSuchMethodException 
	 * @throws IntrospectionException
	 * @throws IllegalArgumentException 
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	public static Map<String, Object> convertBeanToMap(Object bean)
			throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

		if (bean == null) {
			bean = new Result<>();
		}
		Class type = bean.getClass();
		Map<String, Object> returnMap = new HashMap<String, Object>();
		BeanInfo beanInfo = Introspector.getBeanInfo(type);
		PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
		for (int i = 0; i < propertyDescriptors.length; i++) {
			PropertyDescriptor descriptor = propertyDescriptors[i];
			String propertyName = descriptor.getName();
			if (!propertyName.equals("class")) {
				Method readMethod = descriptor.getReadMethod();
				Object result = readMethod.invoke(bean, new Object[0]);
				if (result != null) {
					returnMap.put(propertyName, result);
				} else {
					returnMap.put(propertyName, "");
				}
			}
		}
		return returnMap;
	}

}
